import pygame
from pygame.locals import *

class Gui:
   """Description:
       The primary control class for all gui events and objects.
       
      Useage:
       import pygame
       from Gui import *
       gui = Gui()
       font = pygame.font.get_default_font()
       window = Window((30, 30, 350, 250), 'Test Window', font)
       button = Button((280, 230, 60, 18), "Button", font, 12)
       button.onClick.append(on_test_btn_click)
       window.add(button)
       gui.add(window)
       
       while True:
         gui.update()
         gui.draw()
         pygame.display.flip()
   """
   def __init__(self):
      """ List of Widgets, the objects in this list will be drawn and updated until 
          removed from the list. """
      self._widgets = []
      self._ready = 1
      self._focus = None
      self._dragging = None
      self._drag_count = 0

   def draw(self):
      """ Should be called on every loop that draws to your screen. This function draws 
          all of the Widgets in the list. Objects that can contain children render the
          children themselves. """
      for obj in self._widgets:
         obj.draw()
         
   # 
   def add(self, Widget_):
      """ Adds a Widget to the current list of objects to be rendered and updated"""
      self._widgets.append(Widget_)
   
   def update(self):
      """Should be called every loop that you wish to capture mouse and keyboard events for 
         use with the gui.
         
         TODO: Plenty to be done on the update loop of the gui. I think that the hover events 
         should be called here. Also a good place to see if the content in a window has been 
         changed eg. Should also checks to see if an object should remove itself """
      if self._dragging:
         if not pygame.mouse.get_pressed()[0]:
            print "mouse not pressed, so dropping the drag function"
            self._dragging = None
      events = pygame.event.get()
      for event in events:
         if event.type == pygame.QUIT: self.run = False
         elif event.type == MOUSEBUTTONDOWN:
            clicked = self.getClick(event.pos)
            if clicked:
               self._focus = clicked
               if clicked.can_drag:
                  self._dragging = clicked
         elif event.type == MOUSEMOTION:
            if self._dragging:
               self._dragging.move(event.rel)
               self._drag_count += (abs(event.rel[0])+abs(event.rel[1]))
         elif event.type == MOUSEBUTTONUP:
            if self._dragging:
               self._dragging = None
               self._drag_count = 0
            else:
               clicked = self.getClick(event.pos)
               for a in clicked.onClick:
                  a()
               print clicked
         elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
               self.run = False
            if event.key == K_q:
               self.run = False
         elif event.type == KEYUP:
            print self._focus
            self._char_input(event.key)
   
   def getClick(self, loc):
      """ Returns the Widget that is at screen location loc. 
      
          TODO: should change the name of this function to better represent what it does. 
          Can be used to see what the mouse is hovering over as well. 
          
          TODO: This still needs to take into account what window is on top of the list.
          At the moment it gets fuzzy when clicking on overlapping windows
      """
      x_ = loc[0]
      y_ = loc[1]
      finalObj = None
      for obj in self._widgets:
         if obj.children:
            for child in obj.children:
               if x_ >= child.getX(): 
                  if x_ <= (child.getX() + child.rect.w):
                     if y_ >= child.getY(): 
                        if y_ <= child.getY() + child.rect.h:
                           return child               
         if x_ >= obj.getX(): 
            if x_ <= (obj.getX() + obj.rect.w):
               if y_ >= obj.getY(): 
                  if y_ <= obj.getY() + obj.rect.h:
                     finalObj = obj
      return finalObj

   def _char_input(self, char):
      """Takes care of characters that are input when the gui is actively processing the
         event queue.
         
         TODO: This needs to play nice with shift and filter any chars that produce a 
         glyph. I also assume that this is not safe at all.
      """
      if self._focus:
         print "have widget in focus"
         if hasattr(self._focus, 'add_char'):
            self._focus.add_char(char)
            
   def get_rects(self):
      """Returns a list of rects of _ONLY_ the parent objects eg. windows.
      """
      rects = []
      for g in self._widgets:
         rects.append(g.rect)
      return rects
      
   def draw_debug(self):
      """Draws a debugging window that displays misc information on the state of the gui
         engine.
      """
      font = pygame.font.Font(pygame.font.get_default_font(), 14)
      r = pygame.Rect(300,20,0,0)
      screen = pygame.display.get_surface()
      surfaces = []
      surfaces.append(font.render('Hovering: ' + str(self.getClick(pygame.mouse.get_pos())), 1, (255,255,255)))
      surfaces.append(font.render('Dragging: ' + str(self._dragging), 1, (255,255,255)))
      surfaces.append(font.render('   Distance: ' + str(self._drag_count), 1, (255,255,255)))
      surfaces.append(font.render('Top Window: '+ str(self._widgets[-1]), 1, (255,255,255)))
      surfaces.append(font.render('Focus: ' + str(self._focus), 1, (255,255,255)))
      line = 0
      for s in surfaces:
         screen.blit(s, 
            (r.x,
               r.y + line * s.get_height(),
               r.w, 
               r.h))
         line +=1
   def _set_focus(self):
      print self._widgets

class Widget:
   def __init__(self):
      """ 
         Accepts no args. This is the basic gui object where all others come from. define the parent
         and children lists, set alive to yes, and define the onclick event stack. Finally get the 
         pygame surface 
      """
      self.parent = None
      self.children = []
      self.alive = True
      self.onClick = []
      self.screen = pygame.display.get_surface()
      self.can_drag = False
      self.rect = None
   
   def add(self, widget):
      """ 
         The add method allows you to add any widget to any other widget as a child. It takes the object 
         passes, adds a refrence to the parent attribute and appends the object to the children.
      """
      widget.parent = self
      self.children.append(widget)

   def getX(self):
      """ Returns the self.x taking the parent window into account """
      if self.parent: return self.rect.x + self.parent.rect.x
      else: return self.rect.x
         
   def getY(self):
      """ Returns the self.x taking the parent window into account """
      if self.parent: return self.rect.y + self.parent.rect.y
      else: return self.rect.y

   def move(self, loc):
      """
         Moves the widget by applying a tuple 'loc'.
      """
      self.rect.left += loc[0]
      self.rect.top += loc[1]
      
   def move_to(self, loc):
      """
         Moves the widget to the location specified by 'loc'
      """
      self.rect.left += loc[0]
      self.rect.top += loc[1]
      
   def kill(self):
      """
         simply sets the alive attribute to false.
      """
      self.alive = False

class Text(Widget):
   """ 
      Generic text widget. 
   """
   def __init__(self, rect, text, font, fontsize):
      Widget.__init__(self)
      self.text = text
      self.rect = pygame.Rect(rect[0], rect[1], rect[2], rect[3])
      self.font = pygame.font.Font(font, fontsize)
      self.titleTextSurface = self.font.render(self.text, 1, (255,255,255))
   def draw(self):
      self.screen.blit(self.titleTextSurface, 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y, 
            self.rect.w, 
            self.rect.h))
      for child in self.children:
         child.draw()
         
class Window(Widget):
   """ 
       Base window widget. must be used as a parent for other widgets at the moment.
       TODO: add support for drawing widgets without parents. Maybe some sort of 'blank parent' 
   """
   def __init__(self, rect, title_, font_, drawtop=True):
      Widget.__init__(self)
      self.drawtop = drawtop
      self.titleText = title_
      self.rect = pygame.Rect(rect[0], rect[1], rect[2], rect[3])
      self.title_rect = pygame.Rect
      self.font = pygame.font.Font(font_, 12)
      self.can_drag = True
      self.titleTextSurface = self.font.render(self.titleText, 1, (255,255,255))
      
   def draw(self):
      pygame.draw.rect(self.screen, (61,61,61), self.rect)
      if self.drawtop:
         pygame.draw.rect(self.screen, (100,100,100), 
            (  self.rect.x+3, 
               self.rect.y+3, 
               self.rect.w-6, 
               20 ))
         self.screen.blit(self.titleTextSurface, 
            (  self.rect.x+6,
               self.rect.y+7,
               self.rect.w-4,
               15))
      for child in self.children:
         child.draw()

class Button(Widget):
   """ Simple button widget. TODO: needs hooks for hover. """
   def __init__(self, rect, label_, font_, fontSize_):
      Widget.__init__(self)
      self.label = label_
      self.rect = pygame.Rect(rect[0], rect[1], rect[2], rect[3])
      self.font = pygame.font.Font(font_, fontSize_)
      self.labelSurface = self.font.render(self.label, 1, (255,255,255))
   def draw(self):
      pygame.draw.rect(self.screen, (95,95,95), 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y, 
            self.rect.w, 
            self.rect.h))
      pygame.draw.rect(self.screen, (75,75,75), 
         (  self.parent.rect.x+self.rect.x+1, 
            self.parent.rect.y+self.rect.y+1, 
            self.rect.w-2, 
            self.rect.h-2))
      labelX = (self.getX()) + (self.rect.w/2) - (self.labelSurface.get_width()/2)
      labelY = (self.getY()) + self.rect.h/2 - (self.labelSurface.get_height()/2)
      self.screen.blit(self.labelSurface, ( labelX, labelY, self.rect.w,self.rect.h))
      
class Entry(Widget):
   def __init__(self, x_, y_, chars, buffer_, font_, fontSize_):
      Widget.__init__(self)
      self.font = pygame.font.Font(font_, fontSize_)
      self.rect = pygame.Rect(x_, y_, fontSize_*chars, self.font.get_height())
      self.buffer = buffer_
      self.labelSurface = self.font.render(self.buffer, 1, (255,255,255))
      
   def draw(self):
      pygame.draw.rect(self.screen, (95,95,95), 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y,
            self.rect.w, 
            self.rect.h+4))
      pygame.draw.rect(self.screen, (0,0,0), 
         (  self.parent.rect.x+self.rect.x+1, 
            self.parent.rect.y+self.rect.y+1, 
            self.rect.w-2, 
            self.rect.h-2+4))
      labelX = self.getX() + 4
      labelY = (self.getY()) + self.rect.h/2 - (self.labelSurface.get_height()/2) + 2
      self.screen.blit(self.labelSurface, ( labelX, labelY, self.rect.w,self.rect.h))
      
   def add_char(self, char):
      self.buffer += unichr(char)
      
   def get_buffer(self):
      return self.buffer
      
      
class TextBox(Widget):
   def __init__(self, x, y, number_of_lines, chars_wide, font_, font_size):
      Widget.__init__(self)
      self.font = pygame.font.Font(font_, 12)
      self.rect = pygame.Rect(x, y, chars_wide*font_size,  self.font.get_linesize()*number_of_lines)
      self.num = number_of_lines
      self.buffer = []
      self.buffer_surfaces = []
      for s in range(self.num):
         self.add_line(str(s))
   def draw(self):
      
      pygame.draw.rect(self.screen, (95,95,95), 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y, 
            self.rect.w, self.rect.h+5))
      pygame.draw.rect(self.screen, (0,0,0), 
         (  self.parent.rect.x+self.rect.x+1, 
            self.parent.rect.y+self.rect.y+1, 
            self.rect.w-2, 
            self.rect.h-2+5))
      line = 0
      number_to_pull = len(self.buffer_surfaces) - self.num
      for s in self.buffer_surfaces[number_to_pull:]:
         labelX = self.getX() + 4
         labelY = (self.getY()+2) + line * self.font.get_linesize()
         self.screen.blit(s, ( labelX, labelY, self.rect.w,self.rect.h))
         line +=1

   def add_line(self, line):
      self.buffer.append(line)
      self.buffer_surfaces.append(self.font.render(line, 1, (255,255,255)))

class Dialog(Window):
   def __init__(self, width, height, message="Temp for Dialog Test", ok_call=None, cancel_call=None):
      Widget.__init__(self)
      Window.__init__(self, 
         (  (self.screen.get_width()/2) - (width/2), 
            (self.screen.get_height()/2) - (height/2),
            width, 
            height), 'Question', pygame.font.get_default_font())
      self.add(Button((10, self.rect.h-25, 60, 18), "OK", pygame.font.get_default_font(), 12))
      self.add(Button((self.rect.w-70, self.rect.h-25, 60, 18), "Cancel", pygame.font.get_default_font(), 12))
      self.add(Text((   10, 35, 0, 0), "Would you like to continue?", pygame.font.get_default_font(), 12))

class VScroll(Widget):
   """ Simple scroll widget. """
   def __init__(self, rect):
      Widget.__init__(self)
      self.rect = pygame.Rect(rect[0], rect[1], rect[2], rect[3])
      self.pos = 0
      self.handle_w = 10
      self.can_drag = True
   def draw(self):
      pygame.draw.rect(self.screen, (95,95,95), 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y, 
            self.rect.w, 
            self.rect.h))
      pygame.draw.rect(self.screen, (75,75,75), 
         (  self.parent.rect.x+self.rect.x, 
            self.parent.rect.y+self.rect.y + self.pos, 
            self.rect.w, 
            self.handle_w))

   def move(self, loc):
      if self.pos + loc[1] > 0 and self.pos + loc[1] < self.rect.height - self.handle_w:
         self.pos += loc[1]
      
      
      